#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Author: 邵奈一
@Email: shaonaiyi@163.com
@Date: 2024/11/12
@微信：shaonaiyi888
@微信公众号: 邵奈一 
"""
# 代码3-23
# 使用Embedding类构建训练矩阵
import torch
from torch.autograd import Variable
from torch import nn
print('Embedding类：——————————————————————————')
# 定义一个词语到id的映射字典
word_to_id = {'hello': 0, 'world': 1}
# 定义一个嵌入层，输入维度为2，输出维度为10
# 这里创建了一个Embedding层，输入大小为2（对应两个词语），输出大小为10（每个词语被映射到一个10维的向量）
embeds = nn.Embedding(2, 10)
# 将词语“hello”的ID转换为张量
hello_idx = torch.LongTensor([word_to_id['hello']])
# 通过Embedding层获取其对应的嵌入向量
hello_embed = embeds(hello_idx)
print(hello_embed)

# 输出结果：
# Embedding类：——————————————————————————
# tensor([[ 0.1903,  0.7268, -0.4330, -1.2227, -0.7140,  0.6990,  0.0629, -0.7562,
#          -1.4890, -1.0943]], grad_fn=<EmbeddingBackward>)

# 代码3-24
# 使用SimpleRNN类nn.RNN构建网络
print('使用SimpleRNN类nn.RNN构建网络：——————————————————————————')
# 创建一个形状为 [6, 5, 100] 的随机张量，表示6个时间步，每个时间步有5个样本，每个样本的特征维度为100
x = Variable(torch.randn(6, 5, 100))
# 创建一个 RNN 层，输入特征维度为 100，隐藏状态维度为 200
rnn_seq = nn.RNN(100, 200)
# 打印RNN模型的隐藏状态权重
print(rnn_seq.weight_hh_l0)
# 打印RNN模型的输入权重
print(rnn_seq.weight_ih_l0)
# 使用默认的全 0 隐藏状态对输入x进行处理，得到输出out和最后的隐藏状态h_t
out, h_t = rnn_seq(x)
# 创建一个初始隐藏状态张量，形状为 [1, 5, 200]
h_0 = Variable(torch.randn(1, 5, 200))
# 使用指定的隐藏状态h_0对输入x进行处理，得到输出out和最后的隐藏状态h_t
out, h_t = rnn_seq(x, h_0)
# 打印输出out的形状和最后的隐藏状态h_t的形状
print(out.shape, h_t.shape)

# 输出结果：
# 使用SimpleRNN类nn.RNN构建网络：——————————————————————————
# Parameter containing:
# tensor([[-0.0641, -0.0622, -0.0052,  ...,  0.0490,  0.0702, -0.0689],
#         [ 0.0617, -0.0365,  0.0512,  ...,  0.0029,  0.0696, -0.0409],
#         [ 0.0120, -0.0701,  0.0062,  ...,  0.0300,  0.0348, -0.0134],
#         ...,
#         [ 0.0059,  0.0506,  0.0243,  ..., -0.0098, -0.0040, -0.0516],
#         [-0.0662,  0.0657, -0.0110,  ...,  0.0029, -0.0385, -0.0455],
#         [ 0.0014, -0.0003,  0.0510,  ...,  0.0445,  0.0040,  0.0059]],
#        requires_grad=True)
# Parameter containing:
# tensor([[-0.0097,  0.0230, -0.0454,  ..., -0.0536,  0.0688, -0.0150],
#         [ 0.0098, -0.0547, -0.0011,  ...,  0.0350,  0.0044,  0.0611],
#         [ 0.0559, -0.0045, -0.0613,  ..., -0.0358, -0.0136,  0.0147],
#         ...,
#         [ 0.0016,  0.0365, -0.0412,  ...,  0.0055,  0.0260,  0.0329],
#         [ 0.0018, -0.0663, -0.0037,  ...,  0.0575,  0.0075, -0.0594],
#         [ 0.0244, -0.0315, -0.0556,  ...,  0.0475,  0.0402,  0.0095]],
#        requires_grad=True)
# torch.Size([6, 5, 200]) torch.Size([1, 5, 200])

# 代码3-25
# 使用SimpleRNN类nn.RNNCell构建网络
print('使用SimpleRNN类nn.RNNCell构建网络：——————————————————————————')
# 创建一个RNNCell层，输入维度为100，隐藏层维度为20
cell = nn.RNNCell(100, 20)
# 创建一个形状为 [3, 100] 的随机张量，表示 3 个样本，每个样本的特征维度为 100
x = torch.randn(3, 100)
# 创建一个包含 10 个时间步的输入序列，每个时间步的输入形状为 [3, 100]
xs = [torch.randn(3, 100) for i in range(10)]
# 创建一个初始隐藏状态张量，形状为 [3, 20]，3表示3个样本，20为隐藏层的维度
h = torch.zeros(3, 20)
# 遍历每个时间步的输入
for xt in xs:
    # 使用 RNNCell 进行前向传播，更新隐藏状态
    h = cell(xt, h)
print(h.shape)

# 输出结果：
# 使用SimpleRNN类nn.RNNCell构建网络：——————————————————————————
# torch.Size([3, 20])

# 代码 3-26
# 使用LSTM构建网络
# 打印标题
print('使用LSTM构建网络：——————————————————————————')
# 定义批处理大小
batch_size = 10  # 句子的数量
# 定义序列长度
seq_len = 20  # 每个句子的长度
# 定义词嵌入维度
embedding_dim = 30  # 用长度为30的向量表示一个词语
# 定义词典数量
word_vocab = 100  # 词典的数量（单词的个数）
# 定义隐藏层LSTM的节点数
hidden_size = 18  # 隐藏层中lstm的个数
# 定义隐藏层数量
num_layer = 2  # 多少个隐藏层
# 随机生成一个形状为 [10, 20] 的张量（每个元素是0到99之间的随机整数，代表词汇表中的索引）
in_put = torch.randint(low=0, high=100, size=(batch_size, seq_len))
# 定义词嵌入层（将词汇表中的索引映射到30维的向量）
embedding = torch.nn.Embedding(word_vocab, embedding_dim)
# 定义LSTM层（输入维度为30，隐藏层维度为18，有2层）
lstm = torch.nn.LSTM(embedding_dim, hidden_size, num_layer)
# 词嵌入之后的数据传入lstm
embed = embedding(in_put)  # 得到形状为[10, 20, 30]的张量
# 改变embed的维度顺序（从 [10, 20, 30] 变为 [20, 10, 30]，以适应LSTM的输入要求）
embed = embed.permute(1, 0, 2)  # 得到形状为[20, 10, 30]的张量
# 初始化隐藏层和记忆细胞状态
h_0 = torch.rand(num_layer, batch_size, hidden_size) # 形状为 [2, 10, 18]
c_0 = torch.rand(num_layer, batch_size, hidden_size) # 形状为 [2, 10, 18]
# 输入数据和初始状态传入lstm，得到输出和最后的隐藏层状态
out_put, (h_1, c_1) = lstm(embed, (h_0, c_0))
# 输出输出的大小
print(out_put.size())
# 输出最后的隐藏层状态的大小
print(h_1.size())
# 输出记忆细胞状态的大小
print(c_1.size())
# 获取最后一个输出
last_output = out_put[-1, :, :]
# 输出最后一个输出的大小
print(last_output.size())
# 获取最后一个隐藏层状态
last_hidden_state = h_1[-1, :, :]
# 输出最后一个隐藏层状态的大小
print(last_hidden_state.size())

# 输出结果：
# 使用LSTM构建网络：——————————————————————————
# torch.Size([20, 10, 18])
# torch.Size([2, 10, 18])
# torch.Size([2, 10, 18])
# torch.Size([10, 18])
# torch.Size([10, 18])

# 代码 3-27
# 使用GRU构建网络
print('使用GRU构建网络：——————————————————————————')
gru = nn.GRU(input_size=10, hidden_size=20, num_layers=2)
# 打印 GRU 层的所有参数名称
print(gru._parameters.keys())
# 打印与输入相乘的权重矩阵的形状
print(gru.weight_ih_l0.shape)
# 打印与隐藏状态相乘的权重矩阵的形状
print(gru.weight_hh_l0.shape)

# 输出结果：
# 使用GRU构建网络：——————————————————————————
# odict_keys(['weight_ih_l0', 'weight_hh_l0', 'bias_ih_l0', 'bias_hh_l0', 'weight_ih_l1', 'weight_hh_l1', 'bias_ih_l1', 'bias_hh_l1'])
# torch.Size([60, 10])
# torch.Size([60, 20])